//
//  MKAPITask.m
//  MKAPI
//
//  Created by 苏尚进 on 2022/6/18.
//  Copyright © 2022 moon. All rights reserved.
//

#import "MKAPITask.h"
#import "MKAPIPreCompile.h"

NSErrorUserInfoKey const MKAPITaskErrorTaskKey=@"MKAPITaskErrorTaskKey";
@implementation NSError(MKAPITask)
- (BOOL)mkapi_isTaskCancel{
    return self.code==MKAPITaskCodeCancel && [self.domain isEqualToString:NSStringFromClass([MKAPITask class])];
}
- (BOOL)mkapi_isTaskTimeout{
    return self.code==MKAPITaskCodeTimeout && [self.domain isEqualToString:NSStringFromClass([MKAPITask class])];
}
+ (NSError *)mkapi_errorWithCancelTask:(MKAPITask *)task{
    NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
    if(task){
        info[MKAPITaskErrorTaskKey] = task;
    }
    info[NSLocalizedDescriptionKey] = @"Cancel Task";
    return [NSError errorWithDomain:NSStringFromClass([MKAPITask class]) code:MKAPITaskCodeCancel userInfo:info];
}
+ (NSError *)mkapi_errorWithTimeoutTask:(MKAPITask *)task{
    NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
    if(task){
        info[MKAPITaskErrorTaskKey] = task;
    }
    info[NSLocalizedDescriptionKey] = @"Timeout Task";
    return [NSError errorWithDomain:NSStringFromClass([MKAPITask class]) code:MKAPITaskCodeTimeout userInfo:info];
}
@end

@interface MKAPITask()
@property(assign) NSUInteger taskIdentifier;//任务唯一id值，由任务会话赋值
@property(nonatomic,assign) MKAPITaskSession *taskSession;//所属的会话
@property(nonatomic) MKAPITaskState state;//任务在会话中的状态
@property(nonatomic,copy) NSError *error;
@property(nonatomic,assign) NSUInteger p_completeBlockIndex;
@property(nonatomic,strong) NSMutableArray<MKAPITaskCompleteBlock> *p_completeBlocks;
@property(nonatomic,assign) NSUInteger p_beginRunningBlockIndex;
@property(nonatomic,strong) NSMutableArray<MKAPITaskBeginRunningBlock> *p_beginRunningBlocks;
@property(nonatomic,assign) NSUInteger p_progressBlockIndex;
@property(nonatomic,strong) NSMutableArray<MKAPITaskProgressBlock> *p_progressBlocks;
@property(nonatomic,strong) NSMutableArray<__kindof MKAPITask *> *p_waittingTasks;

@property(nonatomic,strong) NSProgress *progress;
@property(nonatomic,assign) CGFloat p_preProgressPercent;//上一次的进度百分比。在更新进度时，需要保证进度百分比递增。

@property(nonatomic,strong) NSDate *beginRunningDate;//开始执行任务的时间
@property(nonatomic,strong) NSDate *completedDate;//完成任务的时间
@property(nonatomic,strong) NSMutableDictionary<NSString *,NSNumber *> *p_tags;
@property(nonatomic,strong) NSTimer *p_timeoutTimer;
@end

@interface MKAPITaskSession ()
@property(nonatomic,strong) NSOperationQueue *runningQueue;
@property(nonatomic,strong) NSRecursiveLock *p_lock;
@property(nonatomic,assign) NSUInteger p_taskIdentifierIndex;
@property(nonatomic,strong) NSMutableArray<MKAPITask *> *p_tasks;
- (void)finishTask:(MKAPITask *)task;
- (void)runCodeInQueue:(void(^)(void))block;
@end

@implementation MKAPITask
- (id)init{
    if (self=[super init]) {
        self.p_completeBlocks = [[NSMutableArray alloc] init];
        self.p_beginRunningBlocks = [[NSMutableArray alloc] init];
        self.p_progressBlocks = [[NSMutableArray alloc] init];
        self.p_waittingTasks = [[NSMutableArray alloc] init];
        self.p_tags = [[NSMutableDictionary alloc] init];
    }
    return self;
}
+ (nullable NSString *)stateStringWith:(MKAPITaskState)state{
    return @{
        @(MKAPITaskStateCreated):@"MKAPITaskStateCreated",
        @(MKAPITaskStateRunning):@"MKAPITaskStateRunning",
        @(MKAPITaskStateCanceling):@"MKAPITaskStateCanceling",
        @(MKAPITaskStateCompleted):@"MKAPITaskStateCompleted",
    }[@(state)];
}
- (NSString *)description{
    return [NSString stringWithFormat:@"<%@: %p,state=%@,error=%@>",NSStringFromClass(self.class),self,[self.class stateStringWith:self.state],self.error];
}
- (void)dealloc{
#ifdef DEBUG
    NSLog(@"dealloc MKAPITask:<%@: %p>",NSStringFromClass(self.class),self);
#endif
}
- (NSProgress *)progress{
    if(!_progress){
        _progress = [NSProgress progressWithTotalUnitCount:100];
    }
    return _progress;
}
- (NSUInteger)addBeginRunningHandler:(MKAPITaskBeginRunningBlock)block{
    if(!block) return 0;
    self.p_beginRunningBlockIndex++;
    NSUInteger index = self.p_beginRunningBlockIndex;
    [self.p_beginRunningBlocks addObject:[block copy]];
    return index;
}
- (NSUInteger)addCompletionHandler:(MKAPITaskCompleteBlock)block{
    if(!block) return 0;
    if(self.state==MKAPITaskStateCompleted){
        [self.taskSession runCodeInQueue:^{
            block(self,self.error==nil,self.error);
        }];
        return 0;
    }else{
        self.p_completeBlockIndex++;
        NSUInteger index = self.p_completeBlockIndex;
        [self.p_completeBlocks addObject:[block copy]];
        return index;
    }
}
- (NSUInteger)addProgressHandler:(MKAPITaskProgressBlock)block{
    if(!block) return 0;
    self.p_progressBlockIndex++;
    NSUInteger index = self.p_progressBlockIndex;
    [self.p_progressBlocks addObject:[block copy]];
    return index;
}
- (void)waitTasks:(NSArray<MKAPITask *> *)tasks withCompletion:(MKAPITasksCompleteBlock)block{
    @MKAPI_WEAKIFY(self);
    [self waitTasks:tasks withCompletion:block progress:^(NSArray<__kindof MKAPITask *> * _Nonnull tasks, NSProgress * _Nonnull progress, BOOL isFirst) {
        @MKAPI_NORMALIZE(self);
        if(isFirst){
            self.progress.totalUnitCount += progress.totalUnitCount;
            [self.progress addChild:progress withPendingUnitCount:progress.totalUnitCount];
        }
        [self updateProgress:nil];
    }];
}
- (void)waitTask:(MKAPITask *)task withCompletion:(nullable MKAPITaskCompleteBlock2)block{
    NSMutableArray<MKAPITask *> *tasks = [[NSMutableArray alloc] initWithCapacity:1];
    if(task){
        [tasks addObject:task];
    }
    [self waitTasks:tasks withCompletion:^(NSArray<__kindof MKAPITask *> * _Nonnull tasks, BOOL success, NSError * _Nullable error) {
        if(block){
            block(tasks.firstObject,success,error);
        }
    }];
}
- (void)waitTasks:(NSArray<MKAPITask *> *)tasks withCompletion:(nullable MKAPITasksCompleteBlock)block progress:(MKAPITasksProgressBlock)progressBlock{
    NSMutableArray *waittingTasks = self.p_waittingTasks;
    [waittingTasks addObjectsFromArray:tasks];
    if(!self.taskSession){//没有开始任务时，会话为空，此时添加到默认会话中
        [self run];
    }
    [self.taskSession waitTasks:tasks withCompletion:^(NSArray<__kindof MKAPITask *> * _Nonnull tasks, BOOL success, NSError * _Nullable error) {
        [waittingTasks removeObjectsInArray:tasks];
        if(block){
            block(tasks,success,error);
        }
    } progress:progressBlock];
}
- (void)waitTask:(MKAPITask *)task withCompletion:(nullable MKAPITaskCompleteBlock)block progress:(nullable MKAPITaskProgressBlock2)progressBlock{
    NSMutableArray<MKAPITask *> *tasks = [[NSMutableArray alloc] initWithCapacity:1];
    if(task){
        [tasks addObject:task];
    }
    [self waitTasks:tasks withCompletion:^(NSArray<__kindof MKAPITask *> * _Nonnull tasks, BOOL success, NSError * _Nullable error) {
        if(block){
            block(tasks.firstObject,success,error);
        }
    } progress:^(NSArray<__kindof MKAPITask *> * _Nonnull tasks, NSProgress * _Nonnull progress, BOOL isFirst) {
        if(progressBlock){
            progressBlock(tasks.firstObject,progress,isFirst);
        }
    }];
}
- (NSArray<__kindof MKAPITask *> *)waittingTasks{
    return [self.p_waittingTasks copy];
}
- (void)cancelWaittingTasks{
    for (MKAPITask *task in self.waittingTasks) {
        [task cancel];
    }
}
- (void)doRunTask{
}
- (void)doCancel{
}

- (void)completeWithError:(nullable NSError *)error{
    if(self.state==MKAPITaskStateCompleted){
        return;
    }
    self.state = MKAPITaskStateCompleted;
    self.error = error;
    [self __didCompleteWithError:error];
    [self.taskSession runCodeInQueue:^{
        if(!self.error){//完成，强制将进度百分比设置为1
            [self updateProgress:^(NSProgress * _Nonnull progress) {
                self.progress.mkapi_progressPercent = 1;
            }];
        }else{
            [self __notifyUpdateProgress];
        }
        [self.p_progressBlocks removeAllObjects];
        
        NSArray<MKAPITaskCompleteBlock> *blocks = [self.p_completeBlocks copy];
        [self.p_completeBlocks removeAllObjects];
        for (MKAPITaskCompleteBlock block in blocks) {
            block(self,self.error==nil,self.error);
        }
        [self.taskSession finishTask:self];
    }];
}
- (void)updateProgress:(void(^)(NSProgress *progress))handler{
    [self.taskSession runCodeInQueue:^{
        CGFloat preProgressPercent = self.p_preProgressPercent;
        if(handler){
            handler(self.progress);
        }
        CGFloat progressPercent = self.progress.mkapi_progressPercent;
        //限制进度百分比在[0,1]之间
        if(progressPercent<0){
            progressPercent = 0;
            self.progress.mkapi_progressPercent = progressPercent;
        }else if(progressPercent>1){
            progressPercent = 1;
            self.progress.mkapi_progressPercent = progressPercent;
        }
        //限制进度不能回退
        if(progressPercent<preProgressPercent){//进度回退了,此时重设百分比进度值
            self.progress.mkapi_progressPercent = preProgressPercent;
        }
        self.p_preProgressPercent = self.progress.mkapi_progressPercent;
        
        [self __notifyUpdateProgress];
    }];
}
- (void)willBeginRunning{
}
- (void)__beginRunning{
    NSArray<MKAPITaskBeginRunningBlock> *blocks = [self.p_beginRunningBlocks copy];
    [self.p_beginRunningBlocks removeAllObjects];
    for (MKAPITaskBeginRunningBlock block in blocks) {
        block(self);
    }
    [self __notifyUpdateProgress];
    self.beginRunningDate = [NSDate date];
    //设置超时
    if(self.timeoutDuration>0){
        self.p_timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:self.timeoutDuration target:self selector:@selector(__on_p_TimeoutTimer:) userInfo:nil repeats:NO];
    }
    [self willBeginRunning];
}
- (void)__on_p_TimeoutTimer:(NSTimer *)timer{
    NSError *error = [NSError mkapi_errorWithTimeoutTask:self];
    [self completeWithError:error];
}
- (void)__didCompleteWithError:(nullable NSError *)error{
    self.completedDate = [NSDate date];
    if(self.p_timeoutTimer){
        [self.p_timeoutTimer invalidate];
        self.p_timeoutTimer = nil;
    }
    [self didCompleteWithError:error];
}
- (void)didCompleteWithError:(nullable NSError *)error{
}
- (void)__notifyUpdateProgress{
    for (MKAPITaskProgressBlock block in [self.p_progressBlocks copy]) {
        block(self,self.progress);
    }
    [self didUpdateProgress];
}
- (void)didUpdateProgress{
    
}
- (void)addTag:(NSString *)tag{
    if(tag){
        self.p_tags[tag] = @(YES);
    }
}
- (void)removeTag:(NSString *)tag{
    if(tag){
        [self.p_tags removeObjectForKey:tag];
    }
}
- (BOOL)containTag:(NSString *)tag{
    BOOL contain = NO;
    if(tag){
        contain = [self.p_tags objectForKey:tag]!=nil;
    }
    return contain;
}
- (NSArray<NSString *> *)tags{
    return self.p_tags.allKeys;
}

+ (void)createSingleTaskWithCompletion:(void(^)(__kindof MKAPITask *task))completion{
    [[MKAPITaskSession sharedSession] createSingleTaskWithTaskClass:[self class] completion:^(__kindof MKAPITask * _Nonnull task) {
        if(completion){
            completion(task);
        }
    }];
}
@end

@interface MKAPITaskSession()
@property(nonatomic,strong) NSMutableDictionary<NSString *,MKAPITask *> *p_sharedTaskDict;
@end

@implementation MKAPITaskSession
+ (instancetype)sharedSession{
    static MKAPITaskSession *__share__;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        __share__ = [[self alloc] init];
    });
    return __share__;
}
- (id)init{
    return [self initWithRunningQueue:[NSOperationQueue mainQueue]];
}
- (NSString *)description{
    return [NSString stringWithFormat:@"queue:%@,task:%@",self.runningQueue,self.p_tasks];
}
- (id)initWithRunningQueue:(NSOperationQueue *)runningQueue{
    if (self=[super init]) {
        self.runningQueue = runningQueue;
        self.p_sharedTaskDict = [[NSMutableDictionary alloc] init];
        self.p_tasks = [[NSMutableArray alloc] init];
        self.p_lock = [[NSRecursiveLock alloc] init];
    }
    return self;
}
- (void)runTask:(MKAPITask *)task{
    [self.p_lock lock];
    if(task.state==MKAPITaskStateCreated){
        self.p_taskIdentifierIndex++;
        NSInteger index = self.p_taskIdentifierIndex;
        task.taskSession = self;
        task.taskIdentifier = index;
        [self.p_tasks addObject:task];
        task.state = MKAPITaskStateRunning;
        [self runCodeInQueue:^{
            [task __beginRunning];
            [task doRunTask];
        }];
    }
    [self.p_lock unlock];
}
- (void)cancelTasks:(NSArray<MKAPITask *> *)tasks{
    for (MKAPITask *task in tasks) {
        [task cancel];
    }
}
- (void)finishTask:(MKAPITask *)task{
    [self.p_lock lock];
    [self.p_tasks removeObject:task];
    [self.p_lock unlock];
}
- (void)getTasksWithTaskClass:(Class)taskClass completion:(void(^)(NSArray<__kindof MKAPITask *> *tasks))completion{
    [self.p_lock lock];
    NSMutableArray<MKAPITask *> *results = [[NSMutableArray alloc] init];
    if(taskClass) for (MKAPITask *t in [self.p_tasks copy]) {
        if([t isKindOfClass:taskClass]){
            [results addObject:t];
        }
    }
    if(completion){
        completion(results);
    }
    [self.p_lock unlock];
}
- (NSArray<__kindof MKAPITask *> *)getTasksWithTaskClass:(Class)taskClass{
    [self.p_lock lock];
    NSMutableArray<MKAPITask *> *results = [[NSMutableArray alloc] init];
    if(taskClass) for (MKAPITask *t in [self.p_tasks copy]) {
        if([t isKindOfClass:taskClass]){
            [results addObject:t];
        }
    }
    [self.p_lock unlock];
    return results;
}
- (void)getTasksWithTag:(NSString *)tag completion:(void(^)(NSArray<__kindof MKAPITask *> *tasks))completion{
    [self.p_lock lock];
    NSMutableArray<MKAPITask *> *results = [[NSMutableArray alloc] init];
    if(tag) for (MKAPITask *t in [self.p_tasks copy]) {
        if([t containTag:tag]){
            [results addObject:t];
        }
    }
    if(completion){
        completion(results);
    }
    [self.p_lock unlock];
}
- (NSArray<__kindof MKAPITask *> *)getTasksWithTag:(NSString *)tag{
    [self.p_lock lock];
    NSMutableArray<MKAPITask *> *results = [[NSMutableArray alloc] init];
    if(tag) for (MKAPITask *t in [self.p_tasks copy]) {
        if([t containTag:tag]){
            [results addObject:t];
        }
    }
    [self.p_lock unlock];
    return results;
}
- (void)cancelTasksWithTag:(NSString *)tag{
    [self cancelTasks:[self getTasksWithTag:tag]];
}
- (void)createSingleTaskWithTaskClass:(Class)taskClass completion:(void(^)(__kindof MKAPITask *task))completion{
    [self.p_lock lock];
    MKAPITask *result = nil;
    if(taskClass) for (MKAPITask *t in [self.p_tasks copy]) {
        if([t isKindOfClass:taskClass]){
            result = t;
            break;
        }
    }
    if(!result){
        result = [[taskClass alloc] init];
    }
    if(completion){
        completion(result);
    }
    [self.p_lock unlock];
}
- (void)runCodeInQueue:(void(^)(void))block{
    if(!block)return;
    if([NSOperationQueue currentQueue]==self.runningQueue){
        block();
    }else{
        [self.runningQueue addOperationWithBlock:block];
    }
}

- (void)waitTasks:(NSArray<MKAPITask *> *)tasks withCompletion:(MKAPITasksCompleteBlock)block progress:(MKAPITasksProgressBlock)progressBlock{
    [self.p_lock lock];
    if(tasks.count==0){
        if(block){
            [self runCodeInQueue:^{
                block(tasks,YES,nil);
            }];
        }
        [self.p_lock unlock];
        return;
    }
    __block NSInteger successCount = 0;
    for (MKAPITask *task in tasks) {
        if(task.state==MKAPITaskStateCompleted){
            if(task.error){
                if(block){
                    [self runCodeInQueue:^{
                        block(tasks,NO,task.error);
                    }];
                }
                [self.p_lock unlock];
                return;
            }else{
                successCount++;
            }
        }
    }
    if(successCount==tasks.count){
        if(block){
            [self runCodeInQueue:^{
                block(tasks,YES,nil);
            }];
        }
        [self.p_lock unlock];
        return;
    }
    __block BOOL finishedWaiting = NO;
    for (MKAPITask *task in tasks) {
        if(task.state==MKAPITaskStateCompleted){
            continue;
        }
        [task addCompletionHandler:^(__kindof MKAPITask * _Nonnull task, BOOL success, NSError * _Nullable error) {
            if(success){
                successCount++;
                if(successCount>=tasks.count){
                    if(!finishedWaiting){
                        finishedWaiting = YES;
                        if(block){
                            block(tasks,YES,nil);
                        }
                    }
                }
            }else{
                if(!finishedWaiting){
                    finishedWaiting = YES;
                    if(block){
                        block(tasks,NO,error);
                    }
                }
            }
        }];
        if(task.state==MKAPITaskStateCreated){
            [task run];
        }
    }
    
    if(progressBlock){
        NSProgress *joinProgress = [NSProgress progressWithTotalUnitCount:0];
        for (MKAPITask *task in tasks) {
            if(task.state==MKAPITaskStateCompleted){
                continue;
            }
            NSProgress *tmpProgress = [NSProgress progressWithTotalUnitCount:task.progress.totalUnitCount];
            tmpProgress.completedUnitCount = task.progress.mkapi_fractionCompletedUnitCount;
            joinProgress.totalUnitCount += tmpProgress.totalUnitCount;
            [joinProgress addChild:tmpProgress withPendingUnitCount:tmpProgress.totalUnitCount];
            [task addProgressHandler:^(__kindof MKAPITask * _Nonnull task, NSProgress * _Nonnull progress) {
                [tmpProgress mkapi_setCompletedUnitCount:progress.mkapi_fractionCompletedUnitCount totalUnitCount:progress.totalUnitCount];
                if(progressBlock){
                    progressBlock(tasks,joinProgress,NO);
                }
            }];
        }
        if(progressBlock){
            [self runCodeInQueue:^{
                progressBlock(tasks,joinProgress,YES);
            }];
        }
    }
    
    [self.p_lock unlock];
}
- (void)saveSharedTask:(nullable MKAPITask *)task{
    if(!task)return;
    [self.p_lock lock];
    NSMutableDictionary<NSString *,MKAPITask *> *dict = self.p_sharedTaskDict;
    dict[NSStringFromClass(task.class)] = task;
    [self.p_lock unlock];
}
- (void)cleanSharedTask:(Class)taskClass{
    if(!taskClass)return;
    [self.p_lock lock];
    NSMutableDictionary<NSString *,MKAPITask *> *dict = self.p_sharedTaskDict;
    [dict removeObjectForKey:NSStringFromClass(taskClass)];
    [self.p_lock unlock];
}
- (nullable __kindof MKAPITask *)getSharedTask:(Class)taskClass{
    if(!taskClass)return nil;
    [self.p_lock lock];
    NSMutableDictionary<NSString *,MKAPITask *> *dict = self.p_sharedTaskDict;
    MKAPITask *r = dict[NSStringFromClass(taskClass)];
    [self.p_lock unlock];
    return r;
}
@end

@implementation MKAPITask(MKAPITaskSession)
- (void)run{
    [self runInSession:[MKAPITaskSession sharedSession]];
}
- (void)runInSession:(MKAPITaskSession *)session{
    [session runTask:self];
}
- (void)cancel{
    [self.taskSession runCodeInQueue:^{
        if(self.state==MKAPITaskStateRunning||self.state==MKAPITaskStateCreated){
            self.state = MKAPITaskStateCanceling;
            [self doCancel];
        }
    }];
}
@end

@implementation NSProgress(MKAPITask)
- (NSString *)mkapi_description{
    return [NSString stringWithFormat:@"<NSProgress:%p,completedUnitCount:%@,totalUnitCount:%@,fractionCompleted:%@,percent:%@,isIndeterminate:%@>",self,@(self.completedUnitCount),@(self.totalUnitCount),@(self.fractionCompleted),@(self.completedUnitCount*1.0/self.totalUnitCount),@(self.isIndeterminate)];
}
- (CGFloat)mkapi_progressPercent{
    CGFloat percent = self.fractionCompleted;
    return percent;
}
- (int64_t)mkapi_fractionCompletedUnitCount{
    int64_t count = self.mkapi_progressPercent*self.totalUnitCount;
    return count;
}
- (void)setMkapi_progressPercent:(CGFloat)mkapi_progressPercent{
    if(self.totalUnitCount>0){
        CGFloat percent = mkapi_progressPercent;
        percent = MIN(1.0,percent);
        percent = MAX(0,percent);
        self.completedUnitCount = percent*self.totalUnitCount;
    }
}
- (void)mkapi_setCompletedUnitCount:(int64_t)completedUnitCount totalUnitCount:(int64_t)totalUnitCount{
    if(completedUnitCount>self.totalUnitCount){
        //在设置新的完成量与总量时，如果新的完成量>旧的总量，先设置完成量，会导致fractionCompleted计算出错
        self.totalUnitCount = totalUnitCount;
        self.completedUnitCount = completedUnitCount;
    }else{
        self.completedUnitCount = completedUnitCount;
        self.totalUnitCount = totalUnitCount;
    }
}
@end

@implementation NSObject (MKAPI_TaskTag)
- (NSString *)mkapi_addressTag{
    return [NSString stringWithFormat:@"<%@:%p>",NSStringFromClass(self.class),self];
}
- (void)mkapi_runTask:(MKAPITask *)task withCompletion:(void(^ _Nullable)(__kindof id obj,__kindof MKAPITask *task,BOOL success,NSError * _Nullable error))completion{
    if(!task)return;
    [task addTag:self.mkapi_addressTag];
    @MKAPI_WEAKIFY(self);
    [task addCompletionHandler:^(__kindof MKAPITask * _Nonnull task, BOOL success, NSError * _Nullable error) {
        @MKAPI_NORMALIZE(self);
        if(completion){
            completion(self,task,success,error);
        }
    }];
    [task run];
}
- (void)mkapi_cancelWaitingTasks{
    [[MKAPITaskSession sharedSession] cancelTasksWithTag:self.mkapi_addressTag];
}
@end
